/*
 * FreeRTOS+FAT V2.3.3
 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */

/**
 *	@file		ff_fat.c
 *	@ingroup	FAT
 *
 *	@defgroup	FAT Fat File-System
 *	@brief		Handles FAT access and traversal.
 *
 *	Provides file-system interfaces for the FAT file-system.
 **/

#include "ff_headers.h"
#include <string.h>


#if ffconfigFAT_USES_STAT

/* This module make use of a buffer caching called 'FF_FATBuffers_t'.
 * The struct below may gather statistics about its usage: hits/misses.
 */
    struct SFatStat fatStat;
#endif /* ffconfigFAT_USES_STAT */


/* prvGetFromFATBuffers() will see if the FF_Buffer_t pointed to by ppxBuffer contains the
 * buffer that is needed, i.e. opened for the same sector and with the correct R/W mode.
 * If ppxBuffer is NULL or if it can not be used, a new buffer will be created.
 * The buffer pointed to by ppxBuffer will either be released or its pointer will be returned.
 */
FF_Buffer_t * prvGetFromFATBuffers( FF_IOManager_t * pxIOManager,
                                    FF_FATBuffers_t * pxFATBuffers,
                                    BaseType_t xBufferIndex,
                                    uint32_t ulFATSector,
                                    FF_Error_t * pxError,
                                    uint8_t ucMode );

#if ( ffconfigFAT12_SUPPORT != 0 )

/* A very special case for FAT12: an entry is stored in two sectors.
 * Read the two sectors and merge the two values found.
 */
    static uint32_t prvGetFAT12Entry( FF_IOManager_t * pxIOManager,
                                      FF_Error_t * pxError,
                                      FF_FATBuffers_t * pxFATBuffers,
                                      uint32_t ulFATSector );
#endif

#if ( ffconfigFAT12_SUPPORT != 0 )

/* Same as above: put a FAT12 entry that is spread-out over two sectors.
 * Read the current value first to preserve and merge the earlier 4 bits
 * of an adjacent FAT12 entry.
 */
    static FF_Error_t prvPutFAT12Entry( FF_IOManager_t * pxIOManager,
                                        uint32_t ulCluster,
                                        uint32_t ulValue,
                                        FF_FATBuffers_t * pxFATBuffers,
                                        uint32_t ulFATSector );
#endif

#if ( ffconfigFAT12_SUPPORT != 0 )

/* A generic less-optimised way of finding the first free cluster.
 * Used for FAT12 only.
 */
    static uint32_t prvFindFreeClusterSimple( FF_IOManager_t * pxIOManager,
                                              FF_Error_t * pxError );
#endif /* ffconfigFAT12_SUPPORT */

#if ( ffconfigFAT12_SUPPORT != 0 )

/* A generic less-optimised way of counting free clusters.
 * Used for FAT12 only.
 */
    static uint32_t prvCountFreeClustersSimple( FF_IOManager_t * pxIOManager,
                                                FF_Error_t * pxError );
#endif /* ffconfigFAT12_SUPPORT */



/* Have a cluster number and translate it to an LBA (Logical Block Address).
 * 'ulSectorsPerCluster' should be seen as 'blocks per cluster', where the length of one
 * block is defined in the PBR (Partition Boot Record) at FF_FAT_BYTES_PER_SECTOR (offset 0x0B).
 */
uint32_t FF_Cluster2LBA( FF_IOManager_t * pxIOManager,
                         uint32_t ulCluster )
{
    uint32_t ulLBA = 0;
    FF_Partition_t * pxPartition;

    if( pxIOManager != NULL )
    {
        pxPartition = &( pxIOManager->xPartition );

        if( ulCluster >= 2 )
        {
            ulLBA = ( ( ulCluster - 2 ) * pxPartition->ulSectorsPerCluster ) + pxPartition->ulFirstDataSector;
        }
        else
        {
            ulLBA = pxPartition->ulClusterBeginLBA;
        }
    }

    return ulLBA;
}
/*-----------------------------------------------------------*/

/*
 * Major and Minor sectors/blocks:
 *
 * A cluster is defined as N "sectors". Those sectors in fact are "major blocks"
 * whose size is defined in a field called 'FF_FAT_BYTES_PER_SECTOR' in the PBR.
 *
 * I/O to the disk takes place in "minor block" of usually 512 byte and the addressing
 * is also based on "minor block" sector numbers.
 *
 * In most cases, Major == Minor == 512 bytes.
 *
 * Here below some translations are done for 'entries', which can be 1-byte entries
 * as well as the 32-byte directory entries.
 *
 */

/* Translate an 'entry number' (ulEntry) to a relative cluster number,
 * where e.g. 'ulEntry' may be a sequence number of a directory entry for
 * which ulEntrySize = 32 bytes.
 */
uint32_t FF_getClusterChainNumber( FF_IOManager_t * pxIOManager,
                                   uint32_t ulEntry,
                                   uint32_t ulEntrySize )
{
    uint32_t ulBytesPerCluster = pxIOManager->xPartition.usBlkSize * pxIOManager->xPartition.ulSectorsPerCluster;
    uint32_t ulEntriesPerCluster = ( ulBytesPerCluster / ulEntrySize );

    /* E.g. ulBytesPerCluster = 16384, ulEntrySize = 32: 16384 / 32 = 512 entries per cluster. */
    return ulEntry / ulEntriesPerCluster;
}
/*-----------------------------------------------------------*/

/* If the above function returns a cluster number, this function
 * returns a BYTE position within that cluster. */
uint32_t FF_getClusterPosition( FF_IOManager_t * pxIOManager,
                                uint32_t ulEntry,
                                uint32_t ulEntrySize )
{
    uint32_t ulBytesPerCluster = pxIOManager->xPartition.usBlkSize * pxIOManager->xPartition.ulSectorsPerCluster;
    uint32_t ulEntriesPerCluster = ( ulBytesPerCluster / ulEntrySize );

    /* Return the block offset within the current cluster: */
    return ( ulEntry % ulEntriesPerCluster ) * ulEntrySize;
}
/*-----------------------------------------------------------*/

/* Return the block offset (= number of major blocks) within the current cluster: */
uint32_t FF_getMajorBlockNumber( FF_IOManager_t * pxIOManager,
                                 uint32_t ulEntry,
                                 uint32_t ulEntrySize )
{
    uint32_t ulBytesPerCluster = pxIOManager->xPartition.usBlkSize * pxIOManager->xPartition.ulSectorsPerCluster;
    uint32_t ulEntriesPerCluster = ( ulBytesPerCluster / ulEntrySize );
    uint32_t ulRelClusterEntry;

    /* Calculate the entry number within a cluster: */
    ulRelClusterEntry = ulEntry % ulEntriesPerCluster;

    /* Return the block offset within the current cluster: */
    return ulRelClusterEntry / ( pxIOManager->xPartition.usBlkSize / ulEntrySize );
}
/*-----------------------------------------------------------*/

/* Return the minor block number within the current major block */
uint32_t FF_getMinorBlockNumber( FF_IOManager_t * pxIOManager,
                                 uint32_t ulEntry,
                                 uint32_t ulEntrySize )
{
    uint32_t ulBytesPerCluster = pxIOManager->xPartition.usBlkSize * pxIOManager->xPartition.ulSectorsPerCluster;
    uint32_t ulEntriesPerCluster = ( ulBytesPerCluster / ulEntrySize );
    uint32_t ulRelClusterEntry;
    uint32_t ulRelMajorBlockEntry;

    /* Calculate the entry number within a cluster: */
    ulRelClusterEntry = ulEntry % ulEntriesPerCluster;

    ulRelMajorBlockEntry = ulRelClusterEntry % ( pxIOManager->xPartition.usBlkSize / ulEntrySize );

    return ulRelMajorBlockEntry / ( pxIOManager->usSectorSize / ulEntrySize );
}
/*-----------------------------------------------------------*/

/* Get the entry number within the minor block */
uint32_t FF_getMinorBlockEntry( FF_IOManager_t * pxIOManager,
                                uint32_t ulEntry,
                                uint32_t ulEntrySize )
{
    uint32_t ulBytesPerCluster = pxIOManager->xPartition.usBlkSize * pxIOManager->xPartition.ulSectorsPerCluster;
    uint32_t ulEntriesPerCluster = ( ulBytesPerCluster / ulEntrySize );
    uint32_t ulRelClusterEntry;
    uint32_t ulRelMajorBlockEntry;

    /* Calculate the entry number within a cluster: */
    ulRelClusterEntry = ulEntry % ulEntriesPerCluster;

    ulRelMajorBlockEntry = ulRelClusterEntry % ( pxIOManager->xPartition.usBlkSize / ulEntrySize );

    return ulRelMajorBlockEntry % ( pxIOManager->usSectorSize / ulEntrySize );
}
/*-----------------------------------------------------------*/

FF_Error_t FF_ReleaseFATBuffers( FF_IOManager_t * pxIOManager,
                                 FF_FATBuffers_t * pxFATBuffers )
{
    BaseType_t xIndex;
    FF_Error_t xError = FF_ERR_NONE;
    FF_Buffer_t * pxBuffer;

    #if ffconfigBUF_STORE_COUNT != 2
    #warning Only maintaining one FAT table
    #endif

    /* 'ffconfigBUF_STORE_COUNT' equals to the number of FAT tables. */
    for( xIndex = 0; xIndex < ffconfigBUF_STORE_COUNT; xIndex++ )
    {
        pxBuffer = pxFATBuffers->pxBuffers[ xIndex ];

        if( pxBuffer != NULL )
        {
            FF_Error_t xTempError = FF_ERR_NONE;

            pxFATBuffers->pxBuffers[ xIndex ] = NULL;
            xTempError = FF_ReleaseBuffer( pxIOManager, pxBuffer );

            if( FF_isERR( xError ) == pdFALSE )
            {
                /* as everywhere, this function will return
                * the first error that occurred, if any. */
                xError = xTempError;
            }
        }
    }

    #if ffconfigFAT_USES_STAT
    {
        fatStat.clearCount++;
    }
    #endif /* ffconfigFAT_USES_STAT */

    return xError;
}
/*-----------------------------------------------------------*/

FF_Buffer_t * prvGetFromFATBuffers( FF_IOManager_t * pxIOManager,
                                    FF_FATBuffers_t * pxFATBuffers,
                                    BaseType_t xBufferIndex,
                                    uint32_t ulFATSector,
                                    FF_Error_t * pxError,
                                    uint8_t ucMode )
{
    FF_Error_t xError = FF_ERR_NONE;
    FF_Buffer_t * pxBuffer = NULL;

    if( pxFATBuffers != NULL )
    {
        /* See if the same buffer can be reused. */
        pxBuffer = pxFATBuffers->pxBuffers[ xBufferIndex ];

        if( pxBuffer != NULL )
        {
            /* Now the buffer is either owned by pxBuffer,
             *  or it has been released, so put it to NULL. */
            pxFATBuffers->pxBuffers[ xBufferIndex ] = NULL;

            if(
                ( pxBuffer->ulSector == ulFATSector ) &&
                ( ( ( ucMode & FF_MODE_WRITE ) == 0 ) ||
                  ( ( pxBuffer->ucMode & FF_MODE_WRITE ) != 0 ) )
                )
            {
                /* Same sector, AND
                 * write-permission is not required OR the buffer has write permission:
                 * it can be reused. */
                #if ffconfigFAT_USES_STAT
                {
                    fatStat.reuseCount[ ( ucMode & FF_MODE_WRITE ) ? 1 : 0 ]++;
                }
                #endif /* ffconfigFAT_USES_STAT */
            }
            else
            {
                xError = FF_ReleaseBuffer( pxIOManager, pxBuffer );
                pxBuffer = NULL;
                #if ffconfigFAT_USES_STAT
                {
                    fatStat.missCount[ ( ucMode & FF_MODE_WRITE ) ? 1 : 0 ]++;
                }
                #endif /* ffconfigFAT_USES_STAT */
            }
        }
        else
        {
            #if ffconfigFAT_USES_STAT
            {
                fatStat.getCount[ ( ucMode & FF_MODE_WRITE ) ? 1 : 0 ]++;
            }
            #endif /* ffconfigFAT_USES_STAT */
        }
    }

    if( ( pxBuffer == NULL ) && ( FF_isERR( xError ) == pdFALSE ) )
    {
        pxBuffer = FF_GetBuffer( pxIOManager, ulFATSector, ucMode );

        if( pxBuffer == NULL )
        {
            /* Setting an error code without the Module/Function,
             * will be filled-in by the caller. */
            xError = FF_createERR( FF_ERR_DEVICE_DRIVER_FAILED, FF_ERRFLAG );
        }
    }

    *pxError = xError;

    return pxBuffer;
}

#if ( ffconfigFAT12_SUPPORT != 0 )

/* A very special case for FAT12: an entry is stored in two sectors.
 * Read the two sectors and merge the two values found. */
    static uint32_t prvGetFAT12Entry( FF_IOManager_t * pxIOManager,
                                      FF_Error_t * pxError,
                                      FF_FATBuffers_t * pxFATBuffers,
                                      uint32_t ulFATSector )
    {
        FF_Error_t xError = FF_ERR_NONE;
        FF_Buffer_t * pxBuffer = NULL;

        /* preferred buffer access mode, user might want to update this entry
         * and set it to FF_MODE_WRITE. */
        uint8_t ucMode = pxFATBuffers ? pxFATBuffers->ucMode : FF_MODE_READ;
        /* Collect the two bytes in an array. */
        uint8_t ucBytes[ 2 ];
        /* The function return value. */
        uint32_t ulFATEntry = 0UL;

        pxBuffer = prvGetFromFATBuffers( pxIOManager, pxFATBuffers, 0, ulFATSector, &xError, ucMode );

        if( FF_isERR( xError ) )
        {
            xError = FF_createERR( FF_GETERROR( xError ), FF_GETFATENTRY );
        }
        else
        {
            /* Fetch the very last byte of this segment. */
            ucBytes[ 0 ] = FF_getChar( pxBuffer->pucBuffer, ( uint16_t ) ( pxIOManager->usSectorSize - 1 ) );

            xError = FF_ReleaseBuffer( pxIOManager, pxBuffer );

            /* release the other buffer as well. */
            if( ( FF_isERR( xError ) == pdFALSE ) && ( pxFATBuffers != NULL ) )
            {
                xError = FF_ReleaseFATBuffers( pxIOManager, pxFATBuffers );
            }

            if( FF_isERR( xError ) == pdFALSE )
            {
                /* Second Buffer get the first Byte in buffer (second byte of out address)! */
                pxBuffer = FF_GetBuffer( pxIOManager, ulFATSector + 1, ucMode );

                if( pxBuffer == NULL )
                {
                    xError = FF_createERR( FF_ERR_DEVICE_DRIVER_FAILED, FF_GETFATENTRY );
                }
                else
                {
                    /* Read the first byte from the subsequent sector. */
                    ucBytes[ 1 ] = FF_getChar( pxBuffer->pucBuffer, 0 );
                    /* And release that buffer. */
                    xError = FF_ReleaseBuffer( pxIOManager, pxBuffer );

                    if( FF_isERR( xError ) == pdFALSE )
                    {
                        /* Join the two bytes: */
                        ulFATEntry = ( uint32_t ) FF_getShort( ( uint8_t * ) ucBytes, 0 );
                    }
                }
            }
        }

        *pxError = xError;

        return ( int32_t ) ulFATEntry;
    }
#endif /* ffconfigFAT12_SUPPORT */
/*-----------------------------------------------------------*/


/* Get a FAT entry, which is nothing more than a number referring to a sector. */
uint32_t FF_getFATEntry( FF_IOManager_t * pxIOManager,
                         uint32_t ulCluster,
                         FF_Error_t * pxError,
                         FF_FATBuffers_t * pxFATBuffers )
{
    FF_Buffer_t * pxBuffer = NULL;
    uint32_t ulFATOffset;
    uint32_t ulFATSector = 0;
    uint32_t ulFATSectorEntry;
/* The function result. */
    uint32_t ulFATEntry = 0;
    uint32_t ulLBAAdjust;
    uint32_t ulRelClusterEntry = 0;
    FF_Error_t xError = FF_ERR_NONE;
/* preferred mode, user might want to update this entry. */
    uint8_t ucMode = pxFATBuffers ? pxFATBuffers->ucMode : FF_MODE_READ;

    FF_Assert_Lock( pxIOManager, FF_FAT_LOCK );

    if( ulCluster >= pxIOManager->xPartition.ulNumClusters )
    {
        /* _HT_ find a more specific error code.
         * Probably not really important as this is a function internal to the library. */
        xError = FF_createERR( FF_ERR_IOMAN_NOT_ENOUGH_FREE_SPACE, FF_GETFATENTRY );
    }
    else
    {
        if( pxIOManager->xPartition.ucType == FF_T_FAT32 )
        {
            ulFATOffset = ulCluster * 4;
        }
        else if( pxIOManager->xPartition.ucType == FF_T_FAT16 )
        {
            ulFATOffset = ulCluster * 2;
        }
        else /* pxIOManager->xPartition.ucType == FF_T_FAT12 */
        {
            ulFATOffset = ulCluster + ( ulCluster / 2 );
        }

        ulFATSector = pxIOManager->xPartition.ulFATBeginLBA + ( ulFATOffset / pxIOManager->xPartition.usBlkSize );
        ulFATSectorEntry = ulFATOffset % pxIOManager->xPartition.usBlkSize;

        ulLBAAdjust = ulFATSectorEntry / ( ( uint32_t ) pxIOManager->usSectorSize );
        ulRelClusterEntry = ulFATSectorEntry % pxIOManager->usSectorSize;

        ulFATSector = FF_getRealLBA( pxIOManager, ulFATSector );
        ulFATSector += ulLBAAdjust;
    }

    #if ( ffconfigFAT12_SUPPORT != 0 )
        if( ( pxIOManager->xPartition.ucType == FF_T_FAT12 ) &&
            ( FF_isERR( xError ) == pdFALSE ) &&
            ( ulRelClusterEntry == ( uint32_t ) ( ( pxIOManager->usSectorSize - 1 ) ) ) )
        {
            /* Fat Entry SPANS a Sector!
             * It has 4 bits on one sector and 8 bits on the other sector.
             * Handle this in a separate function prvGetFAT12Entry(). */
            ulFATEntry = prvGetFAT12Entry( pxIOManager, &xError, pxFATBuffers, ulFATSector );

            if( ( ulCluster & 0x0001 ) != 0 )
            {
                /* For odd clusters, shift the address 4 bits to the right: */
                ulFATEntry = ( ulFATEntry & 0xfff0 ) >> 4;
            }
            else
            {
                /* For even clusters, take the lower 12 bits: */
                ulFATEntry = ( ulFATEntry & 0x0fff );
            }

            /* Return ulFATEntry, unless xError contains an error. */
        }
        else
    #endif /* ffconfigFAT12_SUPPORT */

    if( FF_isERR( xError ) == pdFALSE )
    {
        /* Handle FAT16, FAT32, and FAT12 (in case the entry lies on a single sector). */

        pxBuffer = prvGetFromFATBuffers( pxIOManager, pxFATBuffers, 0, ulFATSector, &xError, ucMode );

        if( FF_isERR( xError ) )
        {
            xError = FF_createERR( FF_GETERROR( xError ), FF_GETFATENTRY );
        }
        else
        {
            switch( pxIOManager->xPartition.ucType )
            {
                case FF_T_FAT32:
                    ulFATEntry = FF_getLong( pxBuffer->pucBuffer, ulRelClusterEntry );
                    /* Clear the top 4 bits. */
                    ulFATEntry &= 0x0fffffff;
                    break;

                case FF_T_FAT16:
                    ulFATEntry = ( uint32_t ) FF_getShort( pxBuffer->pucBuffer, ulRelClusterEntry );
                    break;

                    #if ( ffconfigFAT12_SUPPORT != 0 )
                        case FF_T_FAT12:
                            ulFATEntry = ( uint32_t ) FF_getShort( pxBuffer->pucBuffer, ulRelClusterEntry );

                            /* Entries are either stored as 4 + 8 bits or as 8 + 4 bits,
                             * depending on the cluster being odd or even.						*/
                            if( ( ulCluster & 0x0001 ) != 0 )
                            {
                                /* For odd clusters, shift the address 4 bits to the right: */
                                ulFATEntry = ( ulFATEntry & 0xfff0 ) >> 4;
                            }
                            else
                            {
                                /* For even clusters, take the lower 12 bits: */
                                ulFATEntry = ( ulFATEntry & 0x0fff );
                            }
                            break;
                    #endif /* if ( ffconfigFAT12_SUPPORT != 0 ) */
                default:
                    ulFATEntry = 0;
                    break;
            }

            if( pxFATBuffers != NULL )
            {
                /* Store the buffer. */
                pxFATBuffers->pxBuffers[ 0 ] = pxBuffer;
            }
            else
            {
                /* Or release it. */
                xError = FF_ReleaseBuffer( pxIOManager, pxBuffer );
            }
        } /* if( FF_isERR( xError ) == pdFALSE ) */
    }     /* else Handle FAT16, FAT32, and FAT12 (in case the entry lies on a single sector). */

    if( FF_isERR( xError ) )
    {
        /* The sector address 0 is not meaningful and here it is used as the 'error value'. */
        ulFATEntry = 0UL;
    }

    if( pxError != NULL )
    {
        *pxError = xError;
    }

    return ulFATEntry;
} /* FF_getFATEntry() */
/*-----------------------------------------------------------*/

/* Write all zero's to all sectors of a given cluster. */
FF_Error_t FF_ClearCluster( FF_IOManager_t * pxIOManager,
                            uint32_t ulCluster )
{
    FF_Error_t xError = FF_ERR_NONE;
    FF_Buffer_t * pxBuffer = NULL;
    BaseType_t xIndex;
    uint32_t ulBaseLBA;

    /* Calculate from cluster number to a real block address. */
    ulBaseLBA = FF_Cluster2LBA( pxIOManager, ulCluster );
    ulBaseLBA = FF_getRealLBA( pxIOManager, ulBaseLBA );

    for( xIndex = 0; xIndex < ( BaseType_t ) pxIOManager->xPartition.ulSectorsPerCluster; xIndex++ )
    {
        if( xIndex == 0 )
        {
            /* When using the FF_MODE_WR_ONLY flag, the data will not be read from disk.
             * Only in the first round a buffer will be claimed. */
            pxBuffer = FF_GetBuffer( pxIOManager, ulBaseLBA, FF_MODE_WR_ONLY );

            if( pxBuffer == NULL )
            {
                xError = FF_createERR( FF_ERR_DEVICE_DRIVER_FAILED, FF_CLEARCLUSTER );
                break;
            }

            memset( pxBuffer->pucBuffer, 0x00, pxIOManager->usSectorSize );
        }

        xError = FF_BlockWrite( pxIOManager, ( uint32_t ) ( ulBaseLBA + xIndex ), 1U, pxBuffer->pucBuffer, pdFALSE );

        if( FF_isERR( xError ) )
        {
            break;
        }
    }

    if( pxBuffer != NULL )
    {
        FF_Error_t xTempError;

        /* The contents of the buffer (all zero's) has been written explicitly to disk
         * by calling FF_BlockWrite().  Therefore, the bModified should be cleared. */
        pxBuffer->bModified = pdFALSE;
        /* Releasing the handle will not write anything */
        xTempError = FF_ReleaseBuffer( pxIOManager, pxBuffer );

        if( FF_isERR( xError ) == pdFALSE )
        {
            xError = xTempError;
        }
    }

    return xError;
}
/*-----------------------------------------------------------*/

/**
 *	@brief	Returns the Cluster address of the Cluster number from the beginning of a chain.
 *
 *	@param	pxIOManager	FF_IOManager_t Object
 *	@param	ulStart		Cluster address of the first cluster in the chain.
 *	@param	ulCount		Number of Cluster in the chain,
 *
 *
 **/
uint32_t FF_TraverseFAT( FF_IOManager_t * pxIOManager,
                         uint32_t ulStart,
                         uint32_t ulCount,
                         FF_Error_t * pxError )
{
    FF_Error_t xError = FF_ERR_NONE;
    uint32_t ulIndex;
    uint32_t ulFatEntry = ulStart;
    uint32_t ulCurrentCluster = ulStart;
    FF_FATBuffers_t xFATBuffers;
    BaseType_t xTakeLock = FF_Has_Lock( pxIOManager, FF_FAT_LOCK ) == pdFALSE;

    /* xFATBuffers is nothing more than an array of FF_Buffer_t's.
     * One buffer for each FAT copy on disk. */
    FF_InitFATBuffers( &xFATBuffers, FF_MODE_READ );

    if( xTakeLock )
    {
        FF_LockFAT( pxIOManager );
    }

    for( ulIndex = 0; ulIndex < ulCount; ulIndex++ )
    {
        ulFatEntry = FF_getFATEntry( pxIOManager, ulCurrentCluster, &xError, &xFATBuffers );

        if( FF_isERR( xError ) )
        {
            ulFatEntry = 0;
            break;
        }

        if( FF_isEndOfChain( pxIOManager, ulFatEntry ) )
        {
            ulFatEntry = ulCurrentCluster;
            break;
        }

        ulCurrentCluster = ulFatEntry;
    }

    if( xTakeLock )
    {
        FF_UnlockFAT( pxIOManager );
    }

    {
        FF_Error_t xTempError;

        xTempError = FF_ReleaseFATBuffers( pxIOManager, &xFATBuffers );

        if( FF_isERR( xError ) == pdFALSE )
        {
            xError = xTempError;
        }
    }

    *pxError = xError;

    return ulFatEntry;
}
/*-----------------------------------------------------------*/

uint32_t FF_FindEndOfChain( FF_IOManager_t * pxIOManager,
                            uint32_t ulStart,
                            FF_Error_t * pxError )
{
    uint32_t ulFatEntry = ulStart;
    FF_Error_t xError;

    if( FF_isEndOfChain( pxIOManager, ulStart ) == pdFALSE )
    {
        /* Traverse FAT for (2^32-1) items/clusters,
         * or until end-of-chain is encountered. */
        ulFatEntry = FF_TraverseFAT( pxIOManager, ulStart, ~( ( uint32_t ) 0U ), &xError );
    }
    else
    {
        xError = FF_ERR_NONE;
    }

    *pxError = xError;

    return ulFatEntry;
}
/*-----------------------------------------------------------*/

/**
 *	@brief	Tests if the ulFATEntry is an End of Chain Marker.
 *
 *	@param	pxIOManager	FF_IOManager_t Object
 *	@param	ulFATEntry	The fat entry from the FAT table to be checked.
 *
 *	@return	pdTRUE if it is an end of chain, otherwise pdFALSE.
 *
 **/
BaseType_t FF_isEndOfChain( FF_IOManager_t * pxIOManager,
                            uint32_t ulFATEntry )
{
    BaseType_t xResult = pdFALSE;

    if( pxIOManager->xPartition.ucType == FF_T_FAT32 )
    {
        if( ( ulFATEntry & 0x0fffffff ) >= 0x0ffffff8 )
        {
            xResult = pdTRUE;
        }
    }
    else if( pxIOManager->xPartition.ucType == FF_T_FAT16 )
    {
        if( ulFATEntry >= 0x0000fff8 )
        {
            xResult = pdTRUE;
        }
    }
    else
    {
        if( ulFATEntry >= 0x00000ff8 )
        {
            xResult = pdTRUE;
        }
    }

    if( ulFATEntry == 0x00000000 )
    {
        xResult = pdTRUE; /* Perhaps trying to read a deleted file! */
    }

    return xResult;
}
/*-----------------------------------------------------------*/

#if ( ffconfigFAT12_SUPPORT != 0 )
    static FF_Error_t prvPutFAT12Entry( FF_IOManager_t * pxIOManager,
                                        uint32_t ulCluster,
                                        uint32_t ulValue,
                                        FF_FATBuffers_t * pxFATBuffers,
                                        uint32_t ulFATSector )
    {
        FF_Buffer_t * pxBuffer = NULL;
        /* For FAT12 FAT Table Across sector boundary traversal. */
        uint8_t ucBytes[ 2 ];
        /* The function result value. */
        uint32_t ulFATEntry;
        FF_Error_t xError = FF_ERR_NONE;
        BaseType_t xIndex;

        #if ( ffconfigWRITE_BOTH_FATS != 0 )
            const BaseType_t xNumFATs = pxIOManager->xPartition.ucNumFATS;
        #else
            const BaseType_t xNumFATs = 1;
        #endif

        /* This routine will only change 12 out of 16 bits.
         * Get the current 16-bit value, 4 bits shall be preserved. */
        ulFATEntry = prvGetFAT12Entry( pxIOManager, &xError, pxFATBuffers, ulFATSector );

        if( FF_isERR( xError ) == pdFALSE )
        {
            if( ( ulCluster & 0x0001 ) != 0 )
            {
                ulFATEntry &= 0x000F;
                ulValue = ( ulValue << 4 );
                ulValue &= 0xFFF0;
            }
            else
            {
                ulFATEntry &= 0xF000;
                ulValue &= 0x0FFF;
            }

            ulFATEntry |= ulValue;

            /* Write at offset 0 in the array ucBytes. */
            FF_putShort( ucBytes, 0, ( uint16_t ) ulFATEntry );

            for( xIndex = 0;
                 xIndex < xNumFATs;
                 xIndex++, ulFATSector += pxIOManager->xPartition.ulSectorsPerFAT )
            {
                /* Write the last byte in the first sector. */
                pxBuffer = FF_GetBuffer( pxIOManager, ulFATSector, FF_MODE_WRITE );
                {
                    if( pxBuffer == NULL )
                    {
                        xError = FF_createERR( FF_ERR_DEVICE_DRIVER_FAILED, FF_PUTFATENTRY );
                        break;
                    }

                    FF_putChar( pxBuffer->pucBuffer, ( uint16_t ) ( pxIOManager->usSectorSize - 1 ), ucBytes[ 0 ] );
                }
                xError = FF_ReleaseBuffer( pxIOManager, pxBuffer );

                if( FF_isERR( xError ) )
                {
                    break;
                }

                /* Write the first byte in the subsequent sector. */
                pxBuffer = FF_GetBuffer( pxIOManager, ulFATSector + 1, FF_MODE_WRITE );
                {
                    if( pxBuffer == NULL )
                    {
                        xError = FF_createERR( FF_ERR_DEVICE_DRIVER_FAILED, FF_PUTFATENTRY );
                        break;
                    }

                    FF_putChar( pxBuffer->pucBuffer, 0x0000, ucBytes[ 1 ] );
                }
                xError = FF_ReleaseBuffer( pxIOManager, pxBuffer );

                if( FF_isERR( xError ) )
                {
                    break;
                }
            } /* for ( xIndex = 0; xIndex < xNumFATs; xIndex++ ) */
        }

        return xError;
    }
#endif /* if ( ffconfigFAT12_SUPPORT != 0 ) */

/**
 *	@brief	Writes a new Entry to the FAT Tables.
 *
 *	@param	pxIOManager		IOMAN object.
 *	@param	ulCluster	Cluster Number to be modified.
 *	@param	ulValue		The value to store.
 **/
FF_Error_t FF_putFATEntry( FF_IOManager_t * pxIOManager,
                           uint32_t ulCluster,
                           uint32_t ulValue,
                           FF_FATBuffers_t * pxFATBuffers )
{
    FF_Buffer_t * pxBuffer;
    uint32_t ulFATOffset;
    uint32_t ulFATSector = 0;
    uint32_t ulFATSectorEntry;
    uint32_t ulFATEntry;
    uint32_t ulLBAAdjust;
    uint32_t ulRelClusterEntry = 0;
    BaseType_t xIndex;
    FF_Error_t xError = FF_ERR_NONE;

    #if ( ffconfigWRITE_BOTH_FATS != 0 )
        const BaseType_t xNumFATs = pxIOManager->xPartition.ucNumFATS;
    #else
        const BaseType_t xNumFATs = 1;
    #endif


    FF_Assert_Lock( pxIOManager, FF_FAT_LOCK );

    /* Avoid corrupting the disk. */
    if( ( ulCluster == 0ul ) || ( ulCluster >= pxIOManager->xPartition.ulNumClusters ) )
    {
        /* find a more specific error code. */
        xError = FF_createERR( FF_ERR_IOMAN_NOT_ENOUGH_FREE_SPACE, FF_PUTFATENTRY );
    }
    else
    {
        if( pxIOManager->xPartition.ucType == FF_T_FAT32 )
        {
            ulFATOffset = ulCluster * 4;
        }
        else if( pxIOManager->xPartition.ucType == FF_T_FAT16 )
        {
            ulFATOffset = ulCluster * 2;
        }
        else /* pxIOManager->xPartition.ucType == FF_T_FAT12 */
        {
            ulFATOffset = ulCluster + ( ulCluster / 2 );
        }

        ulFATSector = pxIOManager->xPartition.ulFATBeginLBA + ( ulFATOffset / pxIOManager->xPartition.usBlkSize );
        ulFATSectorEntry = ulFATOffset % pxIOManager->xPartition.usBlkSize;

        ulLBAAdjust = ulFATSectorEntry / ( ( uint32_t ) pxIOManager->usSectorSize );
        ulRelClusterEntry = ulFATSectorEntry % pxIOManager->usSectorSize;

        ulFATSector = FF_getRealLBA( pxIOManager, ulFATSector );
        ulFATSector += ulLBAAdjust;
    }

    #if ( ffconfigFAT12_SUPPORT != 0 )
        if( ( pxIOManager->xPartition.ucType == FF_T_FAT12 ) &&
            ( FF_isERR( xError ) == pdFALSE ) &&
            ( ulRelClusterEntry == ( uint32_t ) ( ( pxIOManager->usSectorSize - 1 ) ) ) )
        {
            /* The special case in which one FAT12 entries is divided over 2 sectors.
             * Treat this in a separate function. */
            xError = prvPutFAT12Entry( pxIOManager, ulCluster, ulValue, pxFATBuffers, ulFATSector );
            /* Return xError. */
        }
        else
    #endif /* ffconfigFAT12_SUPPORT */

    if( FF_isERR( xError ) == pdFALSE )
    {
        /* Handle FAT16, FAT32, and FAT12 (in case the entry lies on a single sector). */
        for( xIndex = 0;
             xIndex < xNumFATs;
             xIndex++, ulFATSector += pxIOManager->xPartition.ulSectorsPerFAT )
        {
            pxBuffer = prvGetFromFATBuffers( pxIOManager, pxFATBuffers, xIndex, ulFATSector, &xError, FF_MODE_WRITE );

            if( FF_isERR( xError ) )
            {
                xError = FF_createERR( FF_GETERROR( xError ), FF_PUTFATENTRY );
                break;
            }

            if( pxIOManager->xPartition.ucType == FF_T_FAT32 )
            {
                /* Clear the top 4 bits. */
                ulValue &= 0x0fffffff;
                FF_putLong( pxBuffer->pucBuffer, ulRelClusterEntry, ulValue );
            }
            else if( pxIOManager->xPartition.ucType == FF_T_FAT16 )
            {
                FF_putShort( pxBuffer->pucBuffer, ulRelClusterEntry, ( uint16_t ) ulValue );
            }
            else
            {
                ulFATEntry = ( uint32_t ) FF_getShort( pxBuffer->pucBuffer, ulRelClusterEntry );

                if( ( ulCluster & 0x0001 ) != 0 )
                {
                    ulFATEntry &= 0x000F;
                    ulValue = ( ulValue << 4 );
                    ulValue &= 0xFFF0;
                }
                else
                {
                    ulFATEntry &= 0xF000;
                    ulValue &= 0x0FFF;
                }

                FF_putShort( pxBuffer->pucBuffer, ulRelClusterEntry, ( uint16_t ) ( ulFATEntry | ulValue ) );
            }

            if( ( xIndex < ffconfigBUF_STORE_COUNT ) && ( pxFATBuffers != NULL ) )
            {
                /* Store it for later use. */
                pxFATBuffers->pxBuffers[ xIndex ] = pxBuffer;
                pxFATBuffers->ucMode = FF_MODE_WRITE;
            }
            else
            {
                xError = FF_ReleaseBuffer( pxIOManager, pxBuffer );

                if( FF_isERR( xError ) )
                {
                    break;
                }
            }
        }
    }

    /* FF_putFATEntry() returns just an error code, not an address. */
    return xError;
} /* FF_putFATEntry() */
/*-----------------------------------------------------------*/

/**
 *	@brief	Finds a Free Cluster and returns its number.
 *
 *	@param	pxIOManager	IOMAN Object.
 *
 *	@return	The number of the cluster found to be free.
 *	@retval 0 on error.
 **/
#if ( ffconfigFAT12_SUPPORT != 0 )
    static uint32_t prvFindFreeClusterSimple( FF_IOManager_t * pxIOManager,
                                              FF_Error_t * pxError )
    {
        FF_Error_t xError = FF_ERR_NONE;
        uint32_t ulCluster = 0;
        uint32_t ulFATEntry;
        FF_FATBuffers_t xFATBuffers;

        FF_InitFATBuffers( &xFATBuffers, FF_MODE_READ );

        for( ulCluster = pxIOManager->xPartition.ulLastFreeCluster;
             ulCluster < pxIOManager->xPartition.ulNumClusters;
             ulCluster++ )
        {
            ulFATEntry = FF_getFATEntry( pxIOManager, ulCluster, &xError, &xFATBuffers );

            if( FF_isERR( xError ) )
            {
                break;
            }

            if( ulFATEntry == 0 )
            {
                pxIOManager->xPartition.ulLastFreeCluster = ulCluster;
                break;
            }
        }

        {
            FF_Error_t xTempError;

            xTempError = FF_ReleaseFATBuffers( pxIOManager, &xFATBuffers );

            if( FF_isERR( xError ) == pdFALSE )
            {
                xError = xTempError;
            }
        }

        if( ( FF_isERR( xError ) == pdFALSE ) &&
            ( ulCluster == pxIOManager->xPartition.ulNumClusters ) )
        {
            /* There is no free cluster any more. */
            ulCluster = 0;
            xError = FF_createERR( FF_ERR_IOMAN_NOT_ENOUGH_FREE_SPACE, FF_FINDFREECLUSTER );
        }

        *pxError = xError;

        return ulCluster;
    }
#endif /* if ( ffconfigFAT12_SUPPORT != 0 ) */
/*-----------------------------------------------------------*/

uint32_t FF_FindFreeCluster( FF_IOManager_t * pxIOManager,
                             FF_Error_t * pxError,
                             BaseType_t xDoClaim )
{
    FF_Error_t xError = FF_ERR_NONE;
    FF_Buffer_t * pxBuffer = NULL;
    uint32_t x, ulCluster;
    uint32_t ulFATSectorEntry;
    uint32_t ulEntriesPerSector;
    uint32_t ulFATEntry = 1;
    const BaseType_t xEntrySize = ( pxIOManager->xPartition.ucType == FF_T_FAT32 ) ? 4 : 2;
    const uint32_t uNumClusters = pxIOManager->xPartition.ulNumClusters;

    BaseType_t xTakeLock = FF_Has_Lock( pxIOManager, FF_FAT_LOCK ) == pdFALSE;

    if( xTakeLock )
    {
        FF_LockFAT( pxIOManager );
    }

    ulCluster = pxIOManager->xPartition.ulLastFreeCluster;

    #if ( ffconfigFAT12_SUPPORT != 0 )
        /* FAT12 tables are too small to optimise, and would make it very complicated! */
        if( pxIOManager->xPartition.ucType == FF_T_FAT12 )
        {
            ulCluster = prvFindFreeClusterSimple( pxIOManager, &xError );
        }
        else
    #endif
    {
        #if ( ffconfigFSINFO_TRUSTED != 0 )
        {
            /* If 'ffconfigFSINFO_TRUSTED', the contents of the field 'ulLastFreeCluster' is trusted.
             * Only ready it in case of FAT32 and only during the very first time, i.e. when
             * ulLastFreeCluster is still zero. */
            if( ( pxIOManager->xPartition.ucType == FF_T_FAT32 ) && ( pxIOManager->xPartition.ulLastFreeCluster == 0ul ) )
            {
                pxBuffer = FF_GetBuffer( pxIOManager, pxIOManager->xPartition.ulFSInfoLBA, FF_MODE_READ );

                if( pxBuffer == NULL )
                {
                    xError = FF_createERR( FF_ERR_DEVICE_DRIVER_FAILED, FF_FINDFREECLUSTER );
                }
                else
                {
                    if( ( FF_getLong( pxBuffer->pucBuffer, 0 ) == 0x41615252 ) &&
                        ( FF_getLong( pxBuffer->pucBuffer, 484 ) == 0x61417272 ) )
                    {
                        ulCluster = FF_getLong( pxBuffer->pucBuffer, 492 );
                    }

                    xError = FF_ReleaseBuffer( pxIOManager, pxBuffer );
                    pxBuffer = NULL;
                }
            }
        }
        #endif /* if ( ffconfigFSINFO_TRUSTED != 0 ) */

        if( FF_isERR( xError ) == pdFALSE )
        {
            uint32_t ulFATSector;
            uint32_t ulFATOffset;

            ulEntriesPerSector = ( uint32_t ) ( pxIOManager->usSectorSize / xEntrySize );
            ulFATOffset = ( uint32_t ) ( ulCluster * xEntrySize );

            /* Start from a sector where the first free entry is expected,
             * and iterate through every FAT sector. */
            for( ulFATSector = ( ulFATOffset / pxIOManager->xPartition.usBlkSize );
                 ulFATSector < pxIOManager->xPartition.ulSectorsPerFAT;
                 ulFATSector++ )
            {
                pxBuffer = FF_GetBuffer( pxIOManager, pxIOManager->xPartition.ulFATBeginLBA + ulFATSector, FF_MODE_READ );

                if( pxBuffer == NULL )
                {
                    xError = FF_createERR( FF_ERR_DEVICE_DRIVER_FAILED, FF_FINDFREECLUSTER );
                    break;
                }

                for( x = ( ulCluster % ulEntriesPerSector ); x < ulEntriesPerSector; x++ )
                {
                    /* Double-check: don't use non-existing clusters */
                    if( ulCluster >= uNumClusters )
                    {
                        xError = FF_createERR( FF_ERR_IOMAN_NOT_ENOUGH_FREE_SPACE, FF_FINDFREECLUSTER );
                        break;
                    }

                    ulFATSectorEntry = ulFATOffset % pxIOManager->xPartition.usBlkSize;

                    if( pxIOManager->xPartition.ucType == FF_T_FAT32 )
                    {
                        ulFATEntry = FF_getLong( pxBuffer->pucBuffer, ulFATSectorEntry );
                        /* Clear the top 4 bits. */
                        ulFATEntry &= 0x0fffffff;
                    }
                    else
                    {
                        ulFATEntry = ( uint32_t ) FF_getShort( pxBuffer->pucBuffer, ulFATSectorEntry );
                    }

                    if( ulFATEntry == 0x00000000 )
                    {
                        /* Break and return 'ulCluster' */
                        break;
                    }

                    ulFATOffset += xEntrySize;
                    ulCluster++;
                }

                xError = FF_ReleaseBuffer( pxIOManager, pxBuffer );
                pxBuffer = NULL;

                if( FF_isERR( xError ) )
                {
                    break;
                }

                if( ulFATEntry == 0x00000000 )
                {
                    /* And break from the outer loop. */
                    break;
                }
            }

            if( ( FF_isERR( xError ) == pdFALSE ) &&
                ( ulFATSector == pxIOManager->xPartition.ulSectorsPerFAT ) )
            {
                xError = FF_createERR( FF_ERR_IOMAN_NOT_ENOUGH_FREE_SPACE, FF_FINDFREECLUSTER );
            }
        } /* if( FF_isERR( xError ) == pdFALSE ) */
    }     /* if( pxIOManager->xPartition.ucType != FF_T_FAT12 ) */

    if( FF_isERR( xError ) )
    {
        ulCluster = 0UL;
    }

    if( ( ulCluster != 0UL ) && ( xDoClaim != pdFALSE ) )
    {
        FF_Error_t xTempError;

        /* Found a free cluster! */
        pxIOManager->xPartition.ulLastFreeCluster = ulCluster + 1;

        xTempError = FF_putFATEntry( pxIOManager, ulCluster, 0xFFFFFFFF, NULL );

        if( FF_isERR( xError ) == pdFALSE )
        {
            xError = xTempError;
        }

        if( FF_isERR( xError ) )
        {
            ulCluster = 0UL;
        }
    }

    if( xTakeLock )
    {
        FF_UnlockFAT( pxIOManager );
    }

    *pxError = xError;

    return ulCluster;
} /* FF_FindFreeCluster */
/*-----------------------------------------------------------*/

/**
 * @brief	Creates a Cluster Chain
 *	@retval > 0 New created cluster
 *	@retval = 0 See pxError
 **/
uint32_t FF_CreateClusterChain( FF_IOManager_t * pxIOManager,
                                FF_Error_t * pxError )
{
    uint32_t ulStartCluster;
    FF_Error_t xError = FF_ERR_NONE;

    FF_LockFAT( pxIOManager );
    {
        ulStartCluster = FF_FindFreeCluster( pxIOManager, &xError, pdTRUE );
    }
    FF_UnlockFAT( pxIOManager );

    if( ulStartCluster != 0L )
    {
        xError = FF_DecreaseFreeClusters( pxIOManager, 1 );
    }

    *pxError = xError;

    return ulStartCluster;
}
/*-----------------------------------------------------------*/

uint32_t FF_GetChainLength( FF_IOManager_t * pxIOManager,
                            uint32_t ulStartCluster,
                            uint32_t * pulEndOfChain,
                            FF_Error_t * pxError )
{
    uint32_t ulLength = 0;
    FF_FATBuffers_t xFATBuffers;
    FF_Error_t xError = FF_ERR_NONE;

    FF_InitFATBuffers( &xFATBuffers, FF_MODE_READ );

    FF_LockFAT( pxIOManager );
    {
        while( FF_isEndOfChain( pxIOManager, ulStartCluster ) == pdFALSE )
        {
            ulStartCluster = FF_getFATEntry( pxIOManager, ulStartCluster, &xError, &xFATBuffers );

            if( FF_isERR( xError ) )
            {
                ulLength = 0;
                break;
            }

            ulLength++;
        }

        if( pulEndOfChain != NULL )
        {
            /* _HT_
             * ulStartCluster has just been tested as an end-of-chain token.
             * Not sure if the caller expects this. */
            *pulEndOfChain = ulStartCluster;
        }

        xError = FF_ReleaseFATBuffers( pxIOManager, &xFATBuffers );
    }
    FF_UnlockFAT( pxIOManager );

    *pxError = xError;

    return ulLength;
}
/*-----------------------------------------------------------*/

/**
 *	@brief Free's Disk space by freeing unused links on Cluster Chains
 *
 *	@param	pxIOManager	    IOMAN object.
 *	@param	ulStartCluster	Cluster Number that starts the chain.
 *	@param	xDoTruncate		Perform truncation of the FAT entry
 *                          when non-zero - truncate
 *                          when 0 - do not perform truncation
 *
 *	@retval 0 On Success.
 *	@retval	-1 If the device driver failed to provide access.
 *
 **/
FF_Error_t FF_UnlinkClusterChain( FF_IOManager_t * pxIOManager,
                                  uint32_t ulStartCluster,
                                  BaseType_t xDoTruncate )
{
    uint32_t ulFATEntry;
    uint32_t ulCurrentCluster;
    uint32_t ulLength = 0;
    uint32_t ulLastFree = ulStartCluster;
    FF_Error_t xTempError;
    FF_Error_t xError = FF_ERR_NONE;
    FF_FATBuffers_t xFATBuffers;

    BaseType_t xTakeLock = FF_Has_Lock( pxIOManager, FF_FAT_LOCK ) == pdFALSE;

    if( xTakeLock )
    {
        FF_LockFAT( pxIOManager );
    }

    FF_InitFATBuffers( &xFATBuffers, FF_MODE_WRITE );

    ulFATEntry = ulStartCluster;

    /* Free all clusters in the chain! */
    ulCurrentCluster = ulStartCluster;
    ulFATEntry = ulCurrentCluster;

    do
    {
        /* Sector will now be fetched in write-mode. */
        ulFATEntry = FF_getFATEntry( pxIOManager, ulFATEntry, &xError, &xFATBuffers );

        if( FF_isERR( xError ) )
        {
            break;
        }

        if( ( xDoTruncate != pdFALSE ) && ( ulCurrentCluster == ulStartCluster ) )
        {
            xError = FF_putFATEntry( pxIOManager, ulCurrentCluster, 0xFFFFFFFF, &xFATBuffers );
        }
        else
        {
            xError = FF_putFATEntry( pxIOManager, ulCurrentCluster, 0x00000000, &xFATBuffers );
            ulLength++;
        }

        if( FF_isERR( xError ) )
        {
            break;
        }

        if( ulLastFree > ulCurrentCluster )
        {
            ulLastFree = ulCurrentCluster;
        }

        ulCurrentCluster = ulFATEntry;
    } while( FF_isEndOfChain( pxIOManager, ulFATEntry ) == pdFALSE );

    if( FF_isERR( xError ) == pdFALSE )
    {
        if( pxIOManager->xPartition.ulLastFreeCluster > ulLastFree )
        {
            pxIOManager->xPartition.ulLastFreeCluster = ulLastFree;
        }
    }

    xTempError = FF_ReleaseFATBuffers( pxIOManager, &xFATBuffers );

    if( FF_isERR( xError ) == pdFALSE )
    {
        xError = xTempError;
    }

    if( xTakeLock )
    {
        FF_UnlockFAT( pxIOManager );
    }

    if( ulLength != 0 )
    {
        xTempError = FF_IncreaseFreeClusters( pxIOManager, ulLength );

        if( FF_isERR( xError ) == pdFALSE )
        {
            xError = xTempError;
        }
    }

    return xError;
}
/*-----------------------------------------------------------*/

#if ( ffconfigFAT12_SUPPORT != 0 )
    static uint32_t prvCountFreeClustersSimple( FF_IOManager_t * pxIOManager,
                                                FF_Error_t * pxError )
    {
        FF_Error_t xError = FF_ERR_NONE;
        uint32_t ulIndex;
        uint32_t ulFATEntry;
        uint32_t ulFreeClusters = 0;
        const uint32_t xTotalClusters =
            pxIOManager->xPartition.ulDataSectors / pxIOManager->xPartition.ulSectorsPerCluster;

        for( ulIndex = 0; ulIndex < xTotalClusters; ulIndex++ )
        {
            ulFATEntry = FF_getFATEntry( pxIOManager, ulIndex, &xError, NULL );

            if( FF_isERR( xError ) )
            {
                break;
            }

            if( ulFATEntry == 0UL )
            {
                ulFreeClusters++;
            }
        }

        *pxError = xError;

        return ulFreeClusters;
    }
#endif /* if ( ffconfigFAT12_SUPPORT != 0 ) */
/*-----------------------------------------------------------*/


uint32_t FF_CountFreeClusters( FF_IOManager_t * pxIOManager,
                               FF_Error_t * pxError )
{
    FF_Error_t xError = FF_ERR_NONE;
    FF_Buffer_t * pxBuffer;
    uint32_t ulIndex, x;
    uint32_t ulFATEntry;
    uint32_t ulEntriesPerSector;
    uint32_t ulFreeClusters = 0;
    uint32_t ClusterNum = 0;
    BaseType_t xInfoKnown = pdFALSE;
    BaseType_t xTakeLock = FF_Has_Lock( pxIOManager, FF_FAT_LOCK ) == pdFALSE;

    if( xTakeLock )
    {
        FF_LockFAT( pxIOManager );
    }

    #if ( ffconfigFAT12_SUPPORT != 0 )
        /* FAT12 tables are too small to optimise, and would make it very complicated! */
        if( pxIOManager->xPartition.ucType == FF_T_FAT12 )
        {
            ulFreeClusters = prvCountFreeClustersSimple( pxIOManager, &xError );
        }
        else
    #endif
    {
        /* For FAT16 and FAT32 */
        #if ( ffconfigFSINFO_TRUSTED != 0 )
        {
            /* If 'ffconfigFSINFO_TRUSTED', the contents of the field 'ulFreeClusterCount' is trusted. */
            if( pxIOManager->xPartition.ucType == FF_T_FAT32 )
            {
                pxBuffer = FF_GetBuffer( pxIOManager, pxIOManager->xPartition.ulFSInfoLBA, FF_MODE_READ );

                if( pxBuffer == NULL )
                {
                    xError = FF_createERR( FF_ERR_DEVICE_DRIVER_FAILED, FF_COUNTFREECLUSTERS );
                }
                else
                {
                    if( ( FF_getLong( pxBuffer->pucBuffer, 0 ) == 0x41615252 ) &&
                        ( FF_getLong( pxBuffer->pucBuffer, 484 ) == 0x61417272 ) )
                    {
                        ulFreeClusters = FF_getLong( pxBuffer->pucBuffer, 488 );

                        if( ulFreeClusters != ~( ( uint32_t ) 0U ) )
                        {
                            xInfoKnown = pdTRUE;
                        }
                        else
                        {
                            ulFreeClusters = 0ul;
                        }
                    }

                    xError = FF_ReleaseBuffer( pxIOManager, pxBuffer );
                    pxBuffer = NULL;

                    if( xInfoKnown != pdFALSE )
                    {
                        pxIOManager->xPartition.ulFreeClusterCount = ulFreeClusters;
                    }
                }
            }
        }
        #endif /* if ( ffconfigFSINFO_TRUSTED != 0 ) */

        if( ( xInfoKnown == pdFALSE ) && ( pxIOManager->xPartition.usBlkSize != 0 ) )
        {
            if( pxIOManager->xPartition.ucType == FF_T_FAT32 )
            {
                ulEntriesPerSector = pxIOManager->usSectorSize / 4;
            }
            else
            {
                ulEntriesPerSector = pxIOManager->usSectorSize / 2;
            }

            for( ulIndex = 0; ulIndex < pxIOManager->xPartition.ulSectorsPerFAT; ulIndex++ )
            {
                pxBuffer = FF_GetBuffer( pxIOManager, pxIOManager->xPartition.ulFATBeginLBA + ulIndex, FF_MODE_READ );

                if( pxBuffer == NULL )
                {
                    xError = FF_createERR( FF_ERR_DEVICE_DRIVER_FAILED, FF_COUNTFREECLUSTERS );
                    break;
                }

                #if USE_SOFT_WDT
                {
                    /* _HT_ : FF_CountFreeClusters was a little too busy, have it call the WDT and sleep */
                    clearWDT();

                    if( ( ( ulIndex + 1 ) % 32 ) == 0 )
                    {
                        FF_Sleep( 1 );
                    }
                }
                #endif

                for( x = 0; x < ulEntriesPerSector; x++ )
                {
                    if( pxIOManager->xPartition.ucType == FF_T_FAT32 )
                    {
                        /* Clearing the top 4 bits. */
                        ulFATEntry = FF_getLong( pxBuffer->pucBuffer, x * 4 ) & 0x0fffffff;
                    }
                    else
                    {
                        ulFATEntry = ( uint32_t ) FF_getShort( pxBuffer->pucBuffer, x * 2 );
                    }

                    if( ulFATEntry == 0ul )
                    {
                        ulFreeClusters++;
                    }

                    /* FAT table might not be cluster aligned. */
                    if( ClusterNum > pxIOManager->xPartition.ulNumClusters )
                    {
                        /* Stop counting if that's the case. */
                        break;
                    }

                    ClusterNum++;
                }

                xError = FF_ReleaseBuffer( pxIOManager, pxBuffer );
                pxBuffer = NULL;

                if( FF_isERR( xError ) )
                {
                    break;
                }

                if( ClusterNum > pxIOManager->xPartition.ulNumClusters )
                {
                    /* Break out of 2nd loop too ^^ */
                    break;
                }

                /* ulFreeClusters is -2 because the first 2 fat entries in the table are reserved. */
                if( ulFreeClusters > pxIOManager->xPartition.ulNumClusters )
                {
                    ulFreeClusters = pxIOManager->xPartition.ulNumClusters;
                }
            } /* for( ulIndex = 0; ulIndex < pxIOManager->xPartition.ulSectorsPerFAT; ulIndex++ ) */
        }
    }

    if( xTakeLock )
    {
        FF_UnlockFAT( pxIOManager );
    }

    if( FF_isERR( xError ) )
    {
        ulFreeClusters = 0;
    }

    *pxError = xError;

    return ulFreeClusters;
}
/*-----------------------------------------------------------*/

#if ( ffconfig64_NUM_SUPPORT != 0 )
    uint64_t FF_GetFreeSize( FF_IOManager_t * pxIOManager,
                             FF_Error_t * pxError )
    {
        FF_Error_t xError = FF_ERR_NONE;
        uint32_t ulFreeClusters;
        uint64_t ulFreeSize = 0;

        if( pxIOManager != NULL )
        {
            if( pxIOManager->xPartition.ulFreeClusterCount == 0ul )
            {
                FF_LockFAT( pxIOManager );
                {
                    pxIOManager->xPartition.ulFreeClusterCount = FF_CountFreeClusters( pxIOManager, &xError );
                }
                FF_UnlockFAT( pxIOManager );
            }

            ulFreeClusters = pxIOManager->xPartition.ulFreeClusterCount;
            ulFreeSize = ( uint64_t )
                         ( ( uint64_t ) ulFreeClusters * ( uint64_t )
                           ( ( uint64_t ) pxIOManager->xPartition.ulSectorsPerCluster *
                             ( uint64_t ) pxIOManager->xPartition.usBlkSize ) );
        }

        if( pxError != NULL )
        {
            *pxError = xError;
        }

        return ulFreeSize;
    }
#else /* if ( ffconfig64_NUM_SUPPORT != 0 ) */
    uint32_t FF_GetFreeSize( FF_IOManager_t * pxIOManager,
                             FF_Error_t * pxError )
    {
        FF_Error_t xError = FF_ERR_NONE;
        uint32_t ulFreeClusters;
        uint32_t ulFreeSize = 0;

        if( pxIOManager != NULL )
        {
            if( pxIOManager->xPartition.ulFreeClusterCount == 0ul )
            {
                FF_LockFAT( pxIOManager );
                {
                    pxIOManager->xPartition.ulFreeClusterCount = FF_CountFreeClusters( pxIOManager, &xError );
                }
                FF_UnlockFAT( pxIOManager );
            }

            ulFreeClusters = pxIOManager->xPartition.ulFreeClusterCount;
            ulFreeSize = ( uint32_t )
                         ( ( uint32_t ) ulFreeClusters * ( uint32_t )
                           ( ( uint32_t ) pxIOManager->xPartition.ulSectorsPerCluster *
                             ( uint32_t ) pxIOManager->xPartition.usBlkSize ) );
        }

        if( pxError != NULL )
        {
            *pxError = xError;
        }

        return ulFreeSize;
    }
#endif /* ffconfig64_NUM_SUPPORT */
/*-----------------------------------------------------------*/
